/**
 * README
 * 
 * Modificacion de la biblioteca jWave disponible en http://code.google.com/p/jwave/
 * Convertidos los tipos de datos a float para agilizar el tratamiendo de grandes arrays de datos
 * como son los que se tratan en analisis genomicos.
 * Los autores originales de las funciones y del código de la biblioteca cuando fue seleccionada en
 * Abril de 2012 eran:
 *        Christian Scheiblich
 *        Thomas Leduc
 *        sashi
 *        Pol Kennel
 *        Thomas Haider
 *        
 * Por supuesto, todo el merito, honores y demas menciones sobre esta adaptacion les corresponden a ellos
 * y nada mas que a ellos.
 * 
 * 
 * This is a modified version of jWave, available in http://code.google.com/p/jwave/
 * It has been replace the double datatype with float for performance reasons in very big arrays used in
 * bioinformatics like genomic analysis.
 * Original authors of functions and the library when it has been selected in 2012's April were:
 *        Christian Scheiblich
 *        Thomas Leduc
 *        sashi
 *        Pol Kennel
 *        Thomas Haider
 *        
 * Of course, all the merits and mentions about this adaptation have corresponded to them.
 * 
 */

package ev.maths.handlers;

import ev.maths.handlers.wavelets.WaveletInterface;


/**
 * Clase para realizar la transformada de Wavelet discreta
 *
 * @date 01.08.2012
 * @author Enrique Vázquez de Luis
 */
public class DiscreteWaveletTransform extends WaveletTransform {

  
  /**
   * Constructor, recibe un objeto wavelet
   *
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   * @param wavelet La wavelet madre que usará
   */
  public DiscreteWaveletTransform(WaveletInterface wavelet ) {
    super(wavelet);
  }
  
  /**
   * Constructor que recibe la wavelet madre y las iteraciones a realizar
   *
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   * @param wavelet
   */
  public DiscreteWaveletTransform(WaveletInterface wavelet, int iteration ) {
    super(wavelet, iteration);
  }
  
  /**
   * Realiza la transformada discreta unidimensional de wavelet
   * 
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   * @param arrTime coeficientes del dominio del tiempo
   * @see math.transform.jwave.handlers.BasicTransform#forward(double[])
   */
  @Override
  public float[ ] forwardWavelet( float[ ] arrTime ) {
   
    float[ ] arrHilb = new float[ arrTime.length ];
    for( int i = 0; i < arrTime.length; i++ )
      arrHilb[ i ] = arrTime[ i ];

    //int level = 0;
    int h = arrTime.length;
    int minWaveLength = _wavelet.getWaveLength( );
    if( h >= minWaveLength ) {

      while( h >= minWaveLength ) {

        float[ ] iBuf = new float[ h ];

        for( int i = 0; i < h; i++ )
          iBuf[ i ] = arrHilb[ i ];

        float[ ] oBuf = _wavelet.forward( iBuf );

        for( int i = 0; i < h; i++ )
          arrHilb[ i ] = oBuf[ i ];

        h = h >> 1;

        //level++;

      } // levels

    } // if
    
    return arrHilb;
  }

  /**
   * Realiza la transformada inversa discreta de wavelet
   * 
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   * @param arrHilb coeficientes del dominio de Hilbert
   * @see math.transform.jwave.handlers.BasicTransform#reverse(double[])
   */
  @Override
  public float[ ] reverseWavelet( float[ ] arrHilb ) {

    float[ ] arrTime = new float[ arrHilb.length ];

    for( int i = 0; i < arrHilb.length; i++ )
      arrTime[ i ] = arrHilb[ i ];

    //int level = 0;
    int minWaveLength = _wavelet.getWaveLength( );
    int h = minWaveLength;
    if( arrHilb.length >= minWaveLength ) {

      while( h <= arrTime.length && h >= minWaveLength ) {

        float[ ] iBuf = new float[ h ];

        for( int i = 0; i < h; i++ )
          iBuf[ i ] = arrTime[ i ];

        float[ ] oBuf = _wavelet.reverse( iBuf );

        for( int i = 0; i < h; i++ )
          arrTime[ i ] = oBuf[ i ];

        h = h << 1;

        //level++;

      } // levels

    } // if

    return arrTime;
  } // reverse

  /**
   * Realiza la transformada discreta de wavelet hasta un nivel
   * 
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   * @param arrTime coeficientes del dominio del tiempo
   * @param toLevel nivel hasta el que realizar la transformada
   * @see math.transform.jwave.handlers.BasicTransform#forward(double[], int)
   */
  @Override
  public float[ ] forwardWavelet( float[ ] arrTime, int toLevel ) {
    
    float[ ] arrHilb = new float[ arrTime.length ];
    for( int i = 0; i < arrTime.length; i++ )
      arrHilb[ i ] = arrTime[ i ];

    int level = 0;
    int h = arrTime.length;
    int minWaveLength = _wavelet.getWaveLength( );
    if( h >= minWaveLength ) {

      while( h >= minWaveLength && level < toLevel ) {

        float[ ] iBuf = new float[ h ];

        for( int i = 0; i < h; i++ )
          iBuf[ i ] = arrHilb[ i ];

        float[ ] oBuf = _wavelet.forward( iBuf );

        for( int i = 0; i < h; i++ )
          arrHilb[ i ] = oBuf[ i ];

        h = h >> 1;

        level++;

      } // levels

    } // if
    
    return arrHilb;
    
  }

  /**
   * Realiza la transformada inversa discreta de wavelet desde un nivel determinado
   * 
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   * @param arrHilb coeficientes de Hilbert
   * @param fromLevel iteraciones a realizar
   * @see math.transform.jwave.handlers.BasicTransform#reverse(double[], int)
   */
  @Override
  public float[ ] reverseWavelet( float[ ] arrHilb, int fromLevel ) {
    
    float[ ] arrTime = new float[ arrHilb.length ];

    for( int i = 0; i < arrHilb.length; i++ )
      arrTime[ i ] = arrHilb[ i ];

    int level = 0;
    
    int minWaveLength = _wavelet.getWaveLength( );
    
    
    int h = (int)( arrHilb.length / ( Math.pow( 2, fromLevel - 1 ) ) ); 
    
    if( arrHilb.length >= minWaveLength ) {

      while( h <= arrTime.length && h >= minWaveLength && level < fromLevel ) {

        float[ ] iBuf = new float[ h ];

        for( int i = 0; i < h; i++ )
          iBuf[ i ] = arrTime[ i ];

        float[ ] oBuf = _wavelet.reverse( iBuf );

        for( int i = 0; i < h; i++ )
          arrTime[ i ] = oBuf[ i ];

        h = h << 1;

        level++;

      } // levels

    } // if

    return arrTime;
    
  }
  
  /**
   * Realiza la transformada bidimensional discreta de wavelet
   * 
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   * @param matTime coeficientes del dominio del tiempo
   * @see math.transform.jwave.handlers.DiscreteWaveletTransform#forward(double[][])
   */
  public float[ ][ ] forwardWavelet( float[ ][ ] matTime ) {
    
    int noOfRows = matTime.length;
    int noOfCols = matTime[ 0 ].length;
    float[ ][ ] matHilb = new float[ noOfRows ][ noOfCols ];
    for( int i = 0; i < matHilb[ 0 ].length; i++ )
      for( int j = 0; j < matHilb.length; j++ )
        matHilb[ i ][ j ] = matTime[ i ][ j ];

    int h = Math.min( noOfRows, noOfCols );
    int minWaveLength = _wavelet.getWaveLength( );
    
    if( h >= minWaveLength ) {
      while( h >= minWaveLength ) {                  
        
        for( int i = 0; i < h; i++ ) {               
          float[ ] arrTime = new float[ h ];
          for( int j = 0; j < h; j++ )
            arrTime[ j ] = matHilb[ i ][ j ];
          
          float[ ] arrHilb = _wavelet.forward( arrTime );
          
          for( int j = 0; j < h; j++ )
            matHilb[ i ][ j ] = arrHilb[ j ];
        }
        
        for( int j = 0; j < h; j++ ) {               
          float[ ] arrTime = new float[ h ];
          for( int i = 0; i < h; i++ )
            arrTime[ i ] = matHilb[ i ][ j ];

          float[ ] arrHilb = _wavelet.forward( arrTime );

          for( int i = 0; i < h; i++ )
            matHilb[ i ][ j ] = arrHilb[ i ];
        } 
        
        h = h >> 1;
      }
    }
    
    return matHilb;
  }

  /**
   * Realiza la transformada inversa bidimensional de wavelet
   * 
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   * @param matHilb coeficientes de Hilbert
   * @see math.transform.jwave.handlers.DiscreteWaveletTransform#reverse(double[][])
   */
  public float[ ][ ] reverseWavelet( float[ ][ ] matHilb ) {
    
    int noOfRows = matHilb.length;
    int noOfCols = matHilb[ 0 ].length;
    float[ ][ ] matTime = new float[ noOfRows ][ noOfCols ];
    for( int i = 0; i < matHilb[ 0 ].length; i++ )
      for( int j = 0; j < matHilb.length; j++ )
        matTime[ i ][ j ] = matHilb[ i ][ j ];

    int dim = Math.min( noOfRows, noOfCols );
    int minWaveLength = _wavelet.getWaveLength( );
    int h = minWaveLength;
    
    if( h >= minWaveLength ) {
      while( h <= dim && h >= minWaveLength ) {              
        
        for( int i = 0; i < h; i++ ) {                       
          float[ ] arrHilb = new float[ h ];
          for( int j = 0; j < h; j++ )
            arrHilb[ j ] = matTime[ i ][ j ];
          
          float[ ] arrTime = _wavelet.reverse( arrHilb );
          
          for( int j = 0; j < h; j++ )
            matTime[ i ][ j ] = arrTime[ j ];
        }
        
        for( int j = 0; j < h; j++ ) {                      
          float[ ] arrHilb = new float[ h ];
          for( int i = 0; i < h; i++ )
            arrHilb[ i ] = matTime[ i ][ j ];

          float[ ] arrTime = _wavelet.reverse( arrHilb );

          for( int i = 0; i < h; i++ )
            matTime[ i ][ j ] = arrTime[ i ];
        } 
        
        h = h << 1;
      }
    }
    
    return matTime;
  }
  
  /**
   * Realiza la transformada bidimensional de wavelet discreta hasta un límite
   * 
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   * @param matTime coeficientes de tiempo
   * @param toLevel número de iteraciones
   * @see math.transform.jwave.handlers.DiscreteWaveletTransform#forward(double[][])
   */
  @Override
  public float[ ][ ] forwardWavelet( float[ ][ ] matTime, int toLevel ) {
        
    int noOfRows = matTime.length;
    int noOfCols = matTime[ 0 ].length;
    float[ ][ ] matHilb = new float[ noOfRows ][ noOfCols ];
    for( int i = 0; i < matHilb[ 0 ].length; i++ )
      for( int j = 0; j < matHilb.length; j++ )
        matHilb[ i ][ j ] = matTime[ i ][ j ];

    int level = 0;
    int h = Math.min( noOfRows, noOfCols );
    int minWaveLength = _wavelet.getWaveLength( );
    
    if( h >= minWaveLength ) {
      while( h >= minWaveLength && level < toLevel) { 
        
        for( int i = 0; i < h; i++ ) {                
          float[ ] arrTime = new float[ h ];
          for( int j = 0; j < h; j++ )
            arrTime[ j ] = matHilb[ i ][ j ];
          
          float[ ] arrHilb = _wavelet.forward( arrTime );
          
          for( int j = 0; j < h; j++ )
            matHilb[ i ][ j ] = arrHilb[ j ];
        }
        
        for( int j = 0; j < h; j++ ) {                
          float[ ] arrTime = new float[ h ];
          for( int i = 0; i < h; i++ )
            arrTime[ i ] = matHilb[ i ][ j ];

          float[ ] arrHilb = _wavelet.forward( arrTime );

          for( int i = 0; i < h; i++ )
            matHilb[ i ][ j ] = arrHilb[ i ];
        } 
        
        h = h >> 1;
        level++;
        
      }
    }
    
    return matHilb;
  }
  
  /**
   * Realiza la transformada inversa bidimensional discreta de wavelet desde un nivel
   * 
   * @date 01.08.2012
   * @author Enrique Vázquez de Luis
   * @param matHilb coeficientes de Hilbert
   * @param fromLevel nivel de origen
   * @see math.transform.jwave.handlers.DiscreteWaveletTransform#reverse(double[][])
   */
  @Override
  public float[ ][ ] reverseWavelet( float[ ][ ] matHilb, int fromLevel ) {

    int noOfRows = matHilb.length;
    int noOfCols = matHilb[ 0 ].length;
    float[ ][ ] matTime = new float[ noOfRows ][ noOfCols ];
    for( int i = 0; i < matHilb[ 0 ].length; i++ )
      for( int j = 0; j < matHilb.length; j++ )
        matTime[ i ][ j ] = matHilb[ i ][ j ];

    int level = 0;
    int dim = Math.min( noOfRows, noOfCols );
    int minWaveLength = _wavelet.getWaveLength( );
    int h = (int)( dim / Math.pow( 2, fromLevel - 1 ) );
    
    if( h >= minWaveLength ) {

      while( h <= dim && h >= minWaveLength && level < fromLevel) { // levels
        
        for( int i = 0; i < h; i++ ) {                              // rows processing
          float[ ] arrHilb = new float[ h ];
          for( int j = 0; j < h; j++ )
            arrHilb[ j ] = matTime[ i ][ j ];
          
          float[ ] arrTime = _wavelet.reverse( arrHilb );
          
          for( int j = 0; j < h; j++ )
            matTime[ i ][ j ] = arrTime[ j ];
        }
        
        for( int j = 0; j < h; j++ ) {                              // columns processing
          float[ ] arrHilb = new float[ h ];
          for( int i = 0; i < h; i++ )
            arrHilb[ i ] = matTime[ i ][ j ];

          float[ ] arrTime = _wavelet.reverse( arrHilb );

          for( int i = 0; i < h; i++ )
            matTime[ i ][ j ] = arrTime[ i ];
        } 
        
        h = h << 1;
        level++;
      }
    }
    
    return matTime;
  }
  
  

}
